package edu.cicese.ans;

//import org.json.simple.JSONArray;
//import org.json.simple.parser.JSONParser;

import com.jsurf.InterestPoint;
import com.jsurf.SurfUtil;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.util.List;

/*
import surf.Utilities;
import surf.InterestPoint;
import surf.Annotations;
*/

public class Client extends Thread{
	Socket socketService;
	String hostAddress;
	
	public Client(Socket socketService) {
		this.socketService = socketService;
	}
	
	public void run() {
//		System.out.println("Client connected. " + socketService);
		Utilities.print("Client connected. " + socketService + " " + socketService.getInetAddress().getHostName());
//		System.out.println(socketService.getInetAddress().getCanonicalHostName());
//		System.out.println(socketService.getInetAddress().getHostAddress());
//		System.out.println(socketService.getInetAddress().getHostName());
		
		hostAddress = socketService.getInetAddress().getHostAddress();
	//	String hostName = socketService.getInetAddress().getHostName();
		
		try {
			BufferedReader br = new BufferedReader(new InputStreamReader(socketService.getInputStream()));
			JSONObject obj = (JSONObject) JSONValue.parse(br.readLine());
			
			switch (Integer.parseInt(obj.get("command").toString())) {

				case Command.QUERY: //Query
					Utilities.print("[" + Command.QUERY + "] Query.");
					// request image
					requestImage(Long.parseLong(obj.get("fileSize").toString()));

					List<InterestPoint> points = SurfUtil.getInterestPoints(
							Utilities.imagePath + Utilities.imageName + ".jpg", Utilities.TAG_THRESHOLD);
					Utilities.print(points.size() + " points.");
					String[] tags = SurfUtil.getTagsAll(points);
					sendTagsFound(tags[0], tags[1]);


					/*
					// if the user's location is known
					if (Utilities.userLocations != null) {
						//One processing only.
						List<InterestPoint> points = SurfUtil.getInterestPoints(
								Utilities.imagePath + Utilities.imageName + ".jpg", Utilities.TAG_THRESHOLD);*//*
						if (!Utilities.queryOnly) {
							Utilities.print("Adding to sequence.");
							SurfUtil.sequence.remove(0);
							// obtain and store interest-points for location
//							SurfUtil.sequence.add(SurfUtil.getInterestPoints(
//									Utilities.image, Utilities.LOCATION_THRESHOLD));
							//Testing
							SurfUtil.sequence.add(SurfUtil.getInterestPoints(
									Utilities.imagePath + Utilities.imageName + ".jpg", Utilities.LOCATION_THRESHOLD));
							//One processing only.
//							SurfUtil.sequence.add(points);
							if (Utilities.imageNo++ == 3) {
								SurfUtil.locateUser();
								Utilities.imageNo = 0;
								sendUserLocation();
							}
						}

						//check the user location state again, in case the user locations is still unknown
						if (Utilities.userLocations != null) {
							// obtain interest-points for tags
	//						List<InterestPoint> points =
	//							SurfUtil.getInterestPoints(Utilities.image, Utilities.TAG_THRESHOLD);
							//Testing
							List<InterestPoint> points = SurfUtil.getInterestPoints(
									Utilities.imagePath + Utilities.imageName + ".jpg", Utilities.TAG_THRESHOLD);
							String[] tags = SurfUtil.getTags(points, 1);
							sendTagsFound(tags[0], tags[1]);
						}
						else {
							sendACK();
						}
						Utilities.queryOnly = !Utilities.queryOnly;
					}
					else {
						Utilities.print("Adding to sequence.");
						// obtain and store interest-points for location
//						SurfUtil.sequence.add(SurfUtil.getInterestPoints(
//								Utilities.image, Utilities.LOCATION_THRESHOLD));
						SurfUtil.sequence.add(SurfUtil.getInterestPoints(
								Utilities.imagePath + Utilities.imageName + ".jpg", Utilities.LOCATION_THRESHOLD));
						//////////
						//Save log
						Utilities.saveString(Utilities.imagePath + Utilities.imageName + "_99 000", "");
						//////////
						if (SurfUtil.sequence.size() == 3) {
							SurfUtil.locateUser();
							sendUserLocation();
						}
//						else {
							sendACK();
//						}
					}
					*/
					break;

				/*// <<<<< TAGS -----
				case Command.FIND_TAGS: //Query
					Utilities.print("[101] Query.");
					//1. Image request
					requestImage(Long.parseLong(obj.get("fileSize").toString()));
					//2. Obtain interest points
					List<InterestPoint> points =
							SurfUtil.getInterestPoints(Utilities.image, Utilities.TAG_THRESHOLD);
					//3. Make a query (spaceID tags) and send outcome
					*//*if (Utilities.userLocation != null) {
						sendTagsFound(SurfUtil.getTags(points, Utilities.userLocation), Utilities.userLocation);
					}*//*
					if (Utilities.userLocations != null) {
						String[] tags = SurfUtil.getTags(points, 2);
						sendTagsFound(tags[0], tags[1]);
//						sendTagsFound(SurfUtil.getTags(points, Utilities.userLocation), Utilities.userLocation);
					}
					else {
						Utilities.print("UNKNOWN USER LOCATION (Using default location)");
//						try {
//							sleep(10000);
//						} catch (InterruptedException e) {
//							e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
//						}
						Utilities.userLocations = new String[]{"03"};
						String[] tags = SurfUtil.getTags(points, 1);
						sendTagsFound(tags[0], "03");
						Utilities.userLocations = null;

//						JSONObject joTag = SurfUtil.getTags(points);
//						System.out.println(joTag.get("tagID").toString() + " " + joTag.get("spaceID").toString());
//						JSONArray jaTags = new JSONArray();
//						jaTags.add(joTag.get("tagID"));
//						sendTagsFound(jaTags.toJSONString(), joTag.get("spaceID").toString());
					}
					break;
				// ----- TAGS >>>>>*/


				/*// <<<<< USER LOCATION -----
				case Command.LOC_USER: //Locate user
					Utilities.print("[104] User location + 'Image for location' Info.");
					Utilities.imageNo = 1;
					requestImage(Long.parseLong(obj.get("fileSize").toString()));
					sendACK();
					break;
				case Command.IMG_LOC: //'Image for location' info
					Utilities.print("[106] 'Image for location' Info.");
					Utilities.imageNo++;
					requestImage(Long.parseLong(obj.get("fileSize").toString()));
					if (Utilities.imageNo >= 3) {
						SurfUtil.locateUser();
//						SurfUtil.locateUserWithClusters();
						sendUserLocation();
					}
					else {
						sendACK();
					}
					break;
				// ----- USER LOCATION >>>>>*/







				// <<<<< REPOSITORY -----
				case Command.CHK_REP: //Check the changelog
					Utilities.print("[" + Command.CHK_REP + "] Changelog check.");
					sendTagTree();
					break;
				case Command.SEND_REP: //Upload the entire tag repository
					Utilities.print("[" + Command.SEND_REP + "] Repository upload.");
					sendEntireTagTree();
					break;
				case Command.SEND_TAGS: //Send the tags missing or outdated
					Utilities.print("[" + Command.SEND_TAGS + "] Missing tags upload.");
					sendTagFileInfo();
					break;
				case Command.SEND_TAG_FILE: //Send the next tag file in the queue
					Utilities.print("[" + Command.SEND_TAG_FILE + "] Tag file request.");
					sendTagFile();
					break;
				case Command.TAG_FILE_ACK: //Tag file received, continue sending files
					Utilities.print("[" + Command.TAG_FILE_ACK + "] ACK: File " +
							Utilities.filesToUpload.get(0)[0] + " uploaded successfully.");
//					removeFileToUpload();
					Utilities.filesToUpload.remove(0);
					sendTagFileInfo();
					break;
				// ----- REPOSITORY >>>>>
			}
			
			br.close();
			socketService.close();
		}
		catch(IOException e) { e.printStackTrace();	}
	}




	// <<<<< TAGS [FIND_TAGS] -----
	//! Requests an image to find tags;.
	@SuppressWarnings("unchecked")
	private void requestImage(long fileSize) {
		try {
			Utilities.print("Requesting image: " + fileSize + " bytes.");

			Socket socket = new Socket(hostAddress, Utilities.CLIENT_PORT);
			DataInputStream dis = new DataInputStream(socket.getInputStream());
			DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
			
	    	JSONObject jo = new JSONObject();
    		jo.put("command", Command.SEND_IMG);
            dos.writeBytes(jo.toJSONString() + "\n");
            
			//Download image
			downloadImage(dis, Utilities.image, fileSize);
			
			dis.close();
			dos.close();
			socket.close();
		}
		catch(IOException e) { e.printStackTrace();	}
	}
	
	//! Indicates the tags found.
	@SuppressWarnings("unchecked")
	private void sendTagsFound(String tagsFound, String spaceID) {
		Utilities.print("Sending tags found...");
		JSONObject jo = new JSONObject();
		jo.put("command", Command.TAGS_FOUND);
		jo.put("spaceID", spaceID);
		jo.put("tags", tagsFound);
		sendPackage(jo.toJSONString());
	}
	// ----- TAGS [FIND_TAGS] >>>>>
	

	//! Sends the user location.
	@SuppressWarnings("unchecked")
	private void sendUserLocation() {
		Utilities.print("Sending user location...");
		JSONObject jo = new JSONObject();
		jo.put("command", Command.USER_LOC);
//		System.out.println(Utilities.userLocations[0] + " " + Utilities.userLocationStrength);
		if (Utilities.userLocations != null) {
			jo.put("location", Utilities.userLocations[0]);
			jo.put("strength", Utilities.userLocationStrength);
		}
		else {
			jo.put("location", "00");
			jo.put("strength", 0);
			SurfUtil.sequence.clear();
		}
		sendPackage(jo.toJSONString());
	}

	//! Sends an ACK to confirm the reception of an image.
	@SuppressWarnings("unchecked")
	private void sendACK() {
		Utilities.print("Sending image recieved ACK...");
		JSONObject jo = new JSONObject();
		jo.put("command", Command.IMG_ACK);
		sendPackage(jo.toJSONString());
	}

	
	/*// <<<<< USER LOCATION [LOC_USER][IMG_LOC] -----
	//! Requests an image to determine a user's location.
	@SuppressWarnings("unchecked")
	private void requestImage(long fileSize) {
		//1. Image request
//		System.out.println("Requesting image" + Utilities.imageNo + " to locate user.");
		Utilities.print("Requesting image" + Utilities.imageNo + " to locate user.");
		requestImageForLocation(fileSize);
		//2. Obtain interest points
		String imagePath = Utilities.imageForLocation[Utilities.imageNo - 1];
		String imageID = Utilities.filenameWithoutExtension(imagePath);
		SurfUtil.saveToFile(SurfUtil.getInterestPoints(imagePath, Utilities.LOCATION_THRESHOLD),
				Utilities.locationDir + imageID + ".poi");
	}

	//! Requests an image to determine a user's location.
	@SuppressWarnings("unchecked")
	private void requestImageForLocation(long fileSize) {
		try {
			Socket socket = new Socket(hostAddress, Utilities.CLIENT_PORT);
			DataInputStream dis = new DataInputStream(socket.getInputStream());
			DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
			
	    	JSONObject jo = new JSONObject();
    		jo.put("command", Command.SEND_IMG_LOC);
            dos.writeBytes(jo.toJSONString() + "\n");
            
			//Receive image
			downloadImage(dis, Utilities.imageForLocation[Utilities.imageNo - 1], fileSize);

			dis.close();
			dos.close();
			socket.close();
		}
		catch(IOException e) { e.printStackTrace();	}
	}
	
	//! Sends an ACK to confirm the reception of an image.
	@SuppressWarnings("unchecked")
	private void sendACK() {
//		System.out.println("Sending image recieved ACK...");
		Utilities.print("Sending image recieved ACK...");
		JSONObject jo = new JSONObject();
		jo.put("command", Command.IMG_ACK);
		sendPackage(jo.toJSONString());
	}

	//TODO: Remove, test only.
	//! Sends the user location.
	@SuppressWarnings("unchecked")
	private void sendUserLocation() {
//		System.out.println("Sending user location...");
		Utilities.print("Sending user location...");
		JSONObject jo = new JSONObject();
		jo.put("command", Command.USER_LOC);
		*//*if (Utilities.userLocation != null) {
			jo.put("location", Utilities.userLocation);
		}*//*
		if (Utilities.userLocations != null) {
			jo.put("location", Utilities.userLocations[0]);
		}
		else {
			jo.put("location", "00");
		}
		sendPackage(jo.toJSONString());
	}
	// ----- USER LOCATION [LOC_USER][IMG_LOC] >>>>>*/

	
	
	
	// <<<<< REPOSITORY [CHK_REP][SEND_TAGS][SEND_TAG_FILE][TAG_FILE_ACK] -----
	//! Sends the tag repository tree.
	@SuppressWarnings("unchecked")
	private void sendTagTree() {
		Utilities.print("Checking repository...");
		String tagTree = Utilities.getTreeChanges();
		if (!tagTree.equals("[]")) {
			JSONObject jo = new JSONObject();
			Utilities.print("Sending tag tree...");
			jo.put("command", Command.TAG_TREE);
			jo.put("tag_tree", tagTree);
			sendPackage(jo.toJSONString());
		}
		else {
			repositoriesSynchronized();
		}
	}

	//! Sends the tag repository tree.
	@SuppressWarnings("unchecked")
	private void sendEntireTagTree() {
		Utilities.print("Checking repository...");
		String tagTree = Utilities.getTree();
		if (!tagTree.equals("[]")) {
			JSONObject jo = new JSONObject();
			Utilities.print("Sending tag tree...");
			jo.put("command", Command.TAG_TREE);
			jo.put("tag_tree", tagTree);
			sendPackage(jo.toJSONString());
		}
		else {
			repositoriesSynchronized();
		}
	}

	//! Sends a tag file info.
	@SuppressWarnings("unchecked")
	private void sendTagFileInfo() {
		if (!Utilities.filesToUpload.isEmpty()) {
			Utilities.print("Sending tag file info...");
			Object[] fileInfo = Utilities.filesToUpload.get(0);
			JSONObject jo = new JSONObject();
			jo.put("command", Command.FILE_INFO);
			jo.put("filename", fileInfo[0]);
			jo.put("spaceID", fileInfo[1]);
			jo.put("fileSize", fileInfo[2]);
			//TODO: Should I send the filepath as well? so that the client request for a specific file, instead of
			//TODO: requesting the first in the list.
			sendPackage(jo.toJSONString());
		}
		else {
			repositoriesSynchronized();
		}
	}

	//! Sends a tag file.
	private void sendTagFile() {
		try {
			Utilities.print("Sending file " + Utilities.filesToUpload.get(0)[0] + "...");
			DataOutputStream dos = new DataOutputStream(socketService.getOutputStream());
			File file = new File((String)Utilities.filesToUpload.get(0)[3]);
			FileInputStream fis = new FileInputStream(file);
			byte[] outputBuffer = new byte[8192];
			long sendingCount = (long)Math.ceil((double)file.length() / 8192);
			int cont = 1;
			int size = 8192;
			while (cont <= sendingCount) {
				if (cont == sendingCount)
                {
                    size = (int)(file.length() - ((sendingCount - 1) * 8192));
                }
				fis.read(outputBuffer, 0, 8192);
				dos.write(outputBuffer, 0, size);
				cont++;
			}
			fis.close();
			dos.close();
		}
		catch(IOException e) { e.printStackTrace();	}
	}

	//! Sends a notification indicating that the repositories are synchronized.
	@SuppressWarnings("unchecked")
	private void repositoriesSynchronized() {
		// Delete changelog
		Utilities.saveString(Utilities.spaceDir + "changelog.ans", "[]");

		Utilities.print("Repository synchronized.");
		JSONObject jo = new JSONObject();
		jo.put("command", Command.REP_SYNCD);
		sendPackage(jo.toJSONString() + "\n");
	}
	// ----- REPOSITORY [CHK_REP][SEND_TAGS][SEND_TAG_FILE][TAG_FILE_ACK] >>>>>
	



	//! Receives an image.
	private void downloadImage(DataInputStream dis, String imgPath, long fileSize) {
		try {
			Utilities.imageName = System.currentTimeMillis() + "";
			FileOutputStream fos = new FileOutputStream(new File(Utilities.imagePath + Utilities.imageName + ".jpg"));
//			FileOutputStream fos = new FileOutputStream(new File(imgPath));
				
			byte[] inputBuffer = new byte[8192];
	        long readBytes = 0;
			
			while (readBytes < fileSize) {
				int bytes = dis.read(inputBuffer);
				readBytes += bytes;
				fos.write(inputBuffer, 0, bytes);
			}
			
			fos.close();
			Utilities.print("Image received.");
		}
		catch(IOException e) { /*e.printStackTrace();*/	}
	}

	//! Sends a package to the client
	private void sendPackage(String msg) {
		try {
			Socket socket = new Socket(hostAddress, Utilities.CLIENT_PORT);
			DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
            dos.writeBytes(msg + "\n");
			dos.close();
			socket.close();
		}
		catch(IOException e) { e.printStackTrace();	}
	}


	public static void test() {
//		String[] spaceDir = new String[]{
//			"D:\\CICESE\\_tesis\\_experimento\\evaluacion\\vids\\snaps\\chaca1",
//			"D:\\CICESE\\_tesis\\_experimento\\evaluacion\\vids\\snaps\\chaca2",
//			"D:\\CICESE\\_tesis\\_experimento\\evaluacion\\vids\\snaps\\santos1",
//			"D:\\CICESE\\_tesis\\_experimento\\evaluacion\\vids\\snaps\\santos2"
//		};

//		String[] spaceDir = new String[]{
//			"D:\\CICESE\\_tesis\\_experimento\\evaluacion\\imgs\\brubeck\\imgs\\Juego 1\\detectados",
//			"D:\\CICESE\\_tesis\\_experimento\\evaluacion\\imgs\\brubeck\\imgs\\Juego 2\\detectados",
//			"D:\\CICESE\\_tesis\\_experimento\\evaluacion\\imgs\\chaca\\imgs\\Juego 1\\detectados",
//			"D:\\CICESE\\_tesis\\_experimento\\evaluacion\\imgs\\chaca\\imgs\\Juego 2\\detectados",
//			"D:\\CICESE\\_tesis\\_experimento\\evaluacion\\imgs\\hector\\imgs\\Juego 1\\detectados",
//			"D:\\CICESE\\_tesis\\_experimento\\evaluacion\\imgs\\hector\\imgs\\Juego 2\\detectados",
//			"D:\\CICESE\\_tesis\\_experimento\\evaluacion\\imgs\\mabs\\imgs\\Juego 1\\detectados",
//			"D:\\CICESE\\_tesis\\_experimento\\evaluacion\\imgs\\mabs\\imgs\\Juego 2\\detectados",
//			"D:\\CICESE\\_tesis\\_experimento\\evaluacion\\imgs\\roberto\\imgs\\Juego 1\\detectados",
//			"D:\\CICESE\\_tesis\\_experimento\\evaluacion\\imgs\\roberto\\imgs\\Juego 2\\detectados",
//			"D:\\CICESE\\_tesis\\_experimento\\evaluacion\\imgs\\santos\\imgs\\Juego 1\\detectados",
//			"D:\\CICESE\\_tesis\\_experimento\\evaluacion\\imgs\\santos\\imgs\\Juego 2\\detectados",
//		};

		String[] spaceDir = new String[]{
			"D:\\Tests\\Pasitos\\objects\\_otras tarjetas test",
			"D:\\Tests\\Pasitos\\objects\\_test"
		};
		File spaceDirectory = new File(spaceDir[1]);
		FilenameFilter filter = new FilenameFilter() {
      		public boolean accept(File file, String name) {
          		return name.endsWith(".jpg") || name.endsWith(".JPG");
      		}
		};
		File[] images = spaceDirectory.listFiles(filter);
		if (images != null) {
			for (File image : images) {
				try {
					Utilities.imageName = image.getCanonicalPath();
				} catch (IOException e) {
					e.printStackTrace();
				}
				List<InterestPoint> points = SurfUtil.getInterestPoints(
						Utilities.imageName,
						Utilities.TAG_THRESHOLD);
				SurfUtil.saveToFile(points, "D:\\Tests\\Pasitos\\objects\\_pois\\" + image.getName().substring(0, 3) + ".poi");
//				System.out.println(SurfUtil.getTagsAllTest(points));
			}
		}
	}

	public static void test2() {
		File spaceDirectory = new File("D:\\Tests\\Pasitos\\objects\\_pois");
		FilenameFilter filter = new FilenameFilter() {
      		public boolean accept(File file, String name) {
          		return name.endsWith(".poi");
      		}
		};
		File[] pointFiles = spaceDirectory.listFiles(filter);
		if (pointFiles != null) {
			for (File pointFile : pointFiles) {
				List<InterestPoint> points = null;
				try {
					points = SurfUtil.loadInterestPointListFromFile(pointFile.getCanonicalPath());
					Utilities.imageName = pointFile.getName().substring(0, 3);
				} catch (IOException e) {
					e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
				}
//				SurfUtil.saveToFile(points, "D:\\Tests\\Pasitos\\objects\\_pois\\" + image.getName().substring(0, 3) + ".poi");
				System.out.println(SurfUtil.getTagsAllTest(points));
			}
		}
	}
}